iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Software Development

AI 時代的 TDD:讓 AI 寫 Code,但不要讓它決定品質系列 第 22 篇

Day 22:案例——把一個大任務拆成多輪 TDD 循環後,品質有什麼不同

  • 分享至 

  • xImage
  •  

前言:「反正最後都要交出同一份程式碼,拆不拆有差嗎?」

「一個訂單折扣計算功能,規則不算複雜——會員等級折扣、優惠券、滿額贈品,一次講清楚讓 AI 寫完,跟拆成三輪各自寫測試、寫實作、重構,最後產出的程式碼會有差嗎?」

這是我在推動「拆解成多輪 TDD 循環」這條紀律時最常被問到的問題。畢竟如果最後產出的程式碼長得一樣,多花時間拆步驟豈不是白工?今天用一個具體對照告訴你:看起來一樣的需求,兩種做法產出的程式碼,品質差距遠比想像中大——差距不在「有沒有寫測試」,而在測試有沒有真的驗證到規則之間的交互作用。

今日目標

  • 看一組具體對照:同一個訂單折扣需求,「一次到位」vs「拆成三輪 TDD 循環」的產出差異
  • 理解為什麼「一次到位」容易漏掉規則之間的交互情境
  • 認識拆解後測試覆蓋的精確度、程式碼結構清晰度會怎麼不同
  • 建立「規則越多、越該拆」的具體判斷依據

案例:一個有三條規則的訂單折扣功能

需求是這樣:計算一筆訂單的最終金額,要依序套用三條規則——會員等級折扣(金卡 9 折、銀卡 95 折)、滿額贈品(滿 1000 送一份贈品,不影響金額)、優惠券(固定折抵金額,但折抵後金額不得低於 0)。

一次到位生成的版本

❌ 一次生成完整實作 + 測試:
function calculateOrderTotal(order) {
  let total = order.amount;
  if (order.memberLevel === 'gold') total *= 0.9;
  else if (order.memberLevel === 'silver') total *= 0.95;
  if (order.coupon) total = Math.max(0, total - order.coupon.amount);
  return { total, freeGift: order.amount >= 1000 };
}

test('計算訂單總額', () => {
  const result = calculateOrderTotal({ amount: 1000, memberLevel: 'gold', coupon: null });
  expect(result.total).toBe(900);
  expect(result.freeGift).toBe(true);
});

只有一個測試案例,涵蓋的是「金卡會員、滿額、沒有優惠券」這一種組合。程式碼看起來把三條規則都寫了,但沒有任何測試驗證「優惠券折抵後金額變成負數要夾在 0」這個邊界規則,也沒有測試驗證「贈品門檻的判斷基準,到底是折扣前金額還是折扣後金額」這個容易產生歧義的地方——而這個歧義在程式碼裡已經悄悄做了一個選擇(用折扣前的 order.amount 判斷),卻沒有任何測試把這個選擇釘住。

拆成三輪 TDD 循環的版本

拆解後,每一輪只處理一條規則,且每一輪都先問「這條規則跟前面的規則放在一起時,有沒有互相影響的情境要測」:

✅ 第一輪:會員等級折扣(先紅後綠)
test('金卡會員_訂單1000元_應打9折得900元', () => {
  expect(applyMemberDiscount(1000, 'gold')).toBe(900);
});
test('非會員_訂單1000元_應維持原價', () => {
  expect(applyMemberDiscount(1000, null)).toBe(1000);
});

✅ 第二輪:優惠券折抵(加入後才發現要補邊界案例)
test('訂單900元_優惠券折抵200元_應得700元', () => {
  expect(applyCoupon(900, { amount: 200 })).toBe(700);
});
test('訂單100元_優惠券折抵200元_應夾在0元不是負數', () => {
  // 這條測試是寫這一輪時才被逼出來的:
  // 折扣後金額可能小於券面額,這個組合在「一次到位」版本完全沒被想到
  expect(applyCoupon(100, { amount: 200 })).toBe(0);
});

✅ 第三輪:滿額贈品判斷基準(逼出一個原本沒被明講的業務決策)
test('折扣前990元折扣後891元_贈品門檻應以折扣前金額判斷', () => {
  // 寫這一輪測試時,必須先回答「1000元門檻」指的是哪個階段的金額——
  // 這個問題在「一次到位」版本裡是被程式碼悄悄回答的,沒人確認過對不對
  expect(qualifiesForGift(990)).toBe(false);
  expect(qualifiesForGift(1000)).toBe(true);
});

差距從哪裡來:拆解逼出「規則交互情境」

兩個版本的差距,不是「有沒有測試」,而是「每一輪循環開始前,有沒有機會先問一句『這條規則跟前面規則放在一起,有沒有新的情境要處理』。」一次到位生成時,AI 是把整個需求讀完之後直接輸出一份完整答案,思考路徑是「把三條規則的邏輯都寫進同一段程式碼」;拆解成三輪之後,每一輪都是一個獨立的「先想清楚這一條規則的邊界,再動手」的循環,規則之間的交互情境(優惠券折抵後金額變負數、贈品門檻的判斷基準)自然會在寫測試的過程中被逼問出來,而不是被隱性決定掉。

這正是這個系列反覆強調的模式:不是要求 AI 更聰明、想得更周全,而是把一個大問題拆成一連串小問題,讓每個小問題都有機會被單獨檢視。 一次到位的版本不是 AI 能力不夠,是問題的顆粒度太大,任何人(不管是 AI 還是人類工程師)在一次性處理三條交互規則時,都容易漏掉邊界情境。

今日思考題

回想你上一次要求 AI 處理一個有多條規則交互的需求:你是一次講完所有規則讓它一次寫完,還是拆成每條規則各自一輪?如果是前者,你有把握規則之間的交互情境都被想到了嗎?

今日重點回顧

  • 一次到位生成的版本,測試案例只涵蓋開發時想到的那一種規則組合,容易漏掉規則交互情境
  • 拆成多輪 TDD 循環,每一輪開始前都被逼著先想清楚這條規則的邊界,交互情境會在寫測試時自然浮現
  • 差距的根源不是 AI 能力不夠,是問題顆粒度太大時,任何人都容易漏掉邊界
  • 規則越多、規則之間交互越複雜的需求,越值得拆解成多輪循環處理

明日預告

明天要換一個角度看 AI 協作下的 TDD:如果人負責當 navigator(決定方向、審查每一步)、AI 負責當 driver(實際動手寫),這種分工模式具體該怎麼運作。


上一篇
Day 21:怎麼強迫 AI 拆解成小步驟,逐步紅綠重構
系列文
AI 時代的 TDD:讓 AI 寫 Code,但不要讓它決定品質 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言